#include <map>
#include <vector>
#include <set>
#include <queue>
using namespace std;
#include <boost/shared_ptr.hpp>
#include <wx/wx.h>
#include <wx/dir.h>
#include <wx/string.h>
#include <wx/listctrl.h>
#include <wx/gdicmn.h>
#include <wx/volume.h>
#include <wx/file.h>
#include <wx/filename.h>
#include "wx/dnd.h"
#include "wx/dataobj.h"

#include "RWLock.h"
#include "DirectoryWatcher.h"
#include "DirectoryCache.h"
#include "FilePropertiesWatcher.h"
#include "MainListView.h"
#include "ItemInfo.h"
#include "Utils.h"
#include "SyncQueue.h"
#include "BackgroundIconLoader.h"
#include "Application.h"
#include "FileOperation.h"
#include "MainFrame.h"
#include "MainFrameImpl.h"

MainListView::MainListView(wxWindow *parent, unsigned int pId) : wxListView(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_SINGLE_SEL | wxWANTS_CHARS), id(pId), itemDataNeedDeletion(false)
{
    Connect(wxEVT_CONTEXT_MENU, wxContextMenuEventHandler(MainListView::OnContextMenu));
    Connect(wxEVT_COMMAND_LIST_KEY_DOWN, wxListEventHandler(MainListView::OnListKeyDown));
    Connect(wxEVT_COMMAND_LIST_ITEM_ACTIVATED, wxListEventHandler(MainListView::OnActivated));
    Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(MainListView::OnKeyDown));
    Connect(wxEVT_COMMAND_LIST_BEGIN_DRAG, wxListEventHandler(MainListView::OnBeginDrag));
    wxListItem col0;
    col0.SetId(0);
    col0.SetText("Name");
    col0.SetWidth(250);
    InsertColumn(0, col0);

    wxListItem col1;
    col1.SetId(1);
    col1.SetText("Size");
    col1.SetWidth(100);
    InsertColumn(1, col1);

    wxImageList *imageList = new wxImageList;
    imageList->Create(16, 16);
    AssignImageList(imageList, wxIMAGE_LIST_SMALL);

    DirectoryCache *directoryCache = ((App *)wxTheApp)->GetDirectoryCache();
    directoryCache->RegisterWatcher(this);

    SetDropTarget(new FileDropTarget(this));
}
MainListView::~MainListView()
{
    if (itemDataNeedDeletion)
    {
        for (int  i = 0; i < this->GetItemCount(); i++)
            delete ((ItemInfo *) GetItemData(i));
        itemDataNeedDeletion = false;
    }
    Disconnect(wxEVT_CONTEXT_MENU, wxContextMenuEventHandler(MainListView::OnContextMenu));
    Disconnect(wxEVT_COMMAND_LIST_KEY_DOWN, wxListEventHandler(MainListView::OnListKeyDown));
    Disconnect(wxEVT_COMMAND_LIST_ITEM_ACTIVATED, wxListEventHandler(MainListView::OnActivated));
    Disconnect(wxEVT_KEY_DOWN, wxKeyEventHandler(MainListView::OnKeyDown));
    Disconnect(wxEVT_COMMAND_LIST_BEGIN_DRAG, wxListEventHandler(MainListView::OnBeginDrag));
}
void MainListView::OnContextMenu(wxContextMenuEvent& event)
{
    wxPoint point = event.GetPosition();
    // If from keyboard
    if (point.x == -1 && point.y == -1) {
        wxSize size = GetSize();
        point.x = size.x / 2;
        point.y = size.y / 2;
    }

    int flags;
    long subitem;
    long item = HitTest(ScreenToClient(event.GetPosition()), flags, &subitem);
    if (item != -1)
    {
        ItemInfo *info;
        info = (ItemInfo *) GetItemData(item);
        if (info)
        {
            DisplayContextMenu((HWND) GetParent()->GetHWND(), 0, info->fullPath, point.x, point.y);

        }
    }
}
void MainListView::OnListKeyDown(wxListEvent& event)
{
    if (event.GetKeyCode() == WXK_BACK)
    {
        ItemInfo *info = (ItemInfo*) GetItemData(0);
        if (info)
        {
            if (info->name == "..")
            {
                ChangePath(info->fullPath, true);
            }
        }
    }
    else if ((event.GetKeyCode() == WXK_INSERT) && (!currentPath.empty()))
    {
        ItemInfo *info = (ItemInfo*) GetItemData(event.GetIndex());
        if (info)
        {
            if (info->name != "..")
            {
                if (selection.find(event.GetIndex()) != selection.end())
                {
                    selection.erase(event.GetIndex());
                    SetItemTextColour(event.GetIndex(), wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
                }
                else
                {
                    selection.insert(event.GetIndex());
                    SetItemTextColour(event.GetIndex(), colorDatabase.Find("RED"));
                }
            }
            if (event.GetIndex() < GetItemCount() - 1)
            {
                Select(event.GetIndex() + 1);
                Focus(event.GetIndex() + 1);
            }
        }
    }
    else if ((!currentPath.empty()) && ((event.GetKeyCode() == WXK_F5) || (event.GetKeyCode() == WXK_F6) || (event.GetKeyCode() == WXK_F8)))
    {
        FileOperationManager *fopm = ((App*) wxTheApp)->GetFileOperationManager();
        FileOperation fop;
        fop.SetBasePath(currentPath);
        if (event.GetKeyCode() == WXK_F5)
        {
            fop.SetType(FOP_COPY);
            fop.SetDestinationPath(GetOtherMainListView()->GetCurrentPath());
        }
        else if (event.GetKeyCode() == WXK_F6)
        {
            fop.SetType(FOP_MOVE);
            fop.SetDestinationPath(GetOtherMainListView()->GetCurrentPath());
        }
        else
            fop.SetType(FOP_DELETE);

        if (selection.size() == 0)
        {
            ItemInfo *info = (ItemInfo*) GetItemData(event.GetIndex());
            if (info && info->name != "..")
            {
                if (info->isDirectory)
                    fop.AddDirectory(info->fullPath);
                else
                    fop.AddFile(info->fullPath);
            }
        }
        else
        {
            for (set<long>::iterator i = selection.begin(); i != selection.end(); i++)
            {
                ItemInfo *info = (ItemInfo*) GetItemData(*i);
                if (info && info->name != "..")
                {
                    if (info->isDirectory)
                        fop.AddDirectory(info->fullPath);
                    else
                        fop.AddFile(info->fullPath);
                }
            }
        }
        if (!fop.GetDirectoryList().empty() || !fop.GetFileList().empty())
        {
            wxString caption, message, items, destination;
            if (fop.GetFileList().size() == 1 && fop.GetDirectoryList().size() == 0)
                items = fop.GetFileList()[0];
            else if (fop.GetFileList().size() == 0 && fop.GetDirectoryList().size() == 1)
                items = fop.GetDirectoryList()[0];
            else
                items << (fop.GetFileList().size() + fop.GetDirectoryList().size()) << " Items";
            if (fop.GetType() == FOP_COPY)
            {
                caption = "Copy";
                message = "Copy ";
                destination = " To " + fop.GetDestinationPath();
            }
            else if (fop.GetType() == FOP_MOVE)
            {
                caption = "Move";
                message = "Move ";
                destination = " To " + fop.GetDestinationPath();
            }
            else if (fop.GetType() == FOP_DELETE)
            {
                caption = "Delete";
                message = "Delete ";
                destination = "";
            }
            wxMessageDialog dialog(this, message + items + destination + "?", caption, wxYES_NO);
            if (dialog.ShowModal() == wxID_YES)
            {
                wxPanel *panel;
                wxGauge *gauge;
                wxButton *cancelButton;
                wxStaticText *descriptionText;
                MainFrameImpl *frame = ((App*) wxTheApp)->GetMainFrame();
                frame->AddJobWidgets(panel, gauge, cancelButton, descriptionText);
                WorkWatcherPtr workWatcher(new WorkWatcherImpl(panel, gauge, cancelButton, descriptionText));
                fop.SetWorkWatcher(workWatcher);
                fop.RegisterFilePropertiesWatcher(this);
                fop.RegisterFilePropertiesWatcher(GetOtherMainListView());
                cancelButton->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &WorkWatcherImpl::OnCancelButtonPressed, (WorkWatcherImpl*)workWatcher.get());
                fopm->PerformOperation(fop);
            }
        }
    }
    else if ((!currentPath.empty()) && (event.GetKeyCode() == WXK_F7))
    {
        wxTextEntryDialog dialog(this, "Enter directory name:", "New directory");
        if (dialog.ShowModal() == wxID_OK)
        {
            wxFileName dir = wxFileName::DirName(currentPath);
            dir.AppendDir(dialog.GetValue());
            if (!wxMkdir(dir.GetFullPath()))
            {
                wxMessageBox("Can't create directory.", "Error", wxOK | wxICON_ERROR);
            }
        }
    }
    else if ((!currentPath.empty()) && (event.GetKeyCode() == WXK_NUMPAD_MULTIPLY))
    {
        set<long> all;
        for (long i = 0; i < GetItemCount(); i++)
        {
            ItemInfo *info = (ItemInfo*) GetItemData(i);
            if (info && info->name != "..")
            {
                if (selection.find(i) == selection.end())
                {
                    SetItemTextColour(i, colorDatabase.Find("RED"));
                    all.insert(i);
                }
                else
                    SetItemTextColour(i, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
            }
        }
        selection = all;
    }
    else if ((!currentPath.empty()) && (event.GetKeyCode() == WXK_NUMPAD_ADD))
    {
        wxTextEntryDialog dialog(this, "Enter pattern:", "Select");
        if (dialog.ShowModal() == wxID_OK)
        {
            for (long i = 0; i < GetItemCount(); i++)
            {
                ItemInfo *info = (ItemInfo*) GetItemData(i);
                if (info && info->name != "..")
                {
                    if (wxMatchWild(dialog.GetValue(), info->name))
                    {
                        SetItemTextColour(i, colorDatabase.Find("RED"));
                        selection.insert(i);
                    }
                }
            }
        }
    }
    else if ((!currentPath.empty()) && (event.GetKeyCode() == WXK_NUMPAD_SUBTRACT))
    {
        wxTextEntryDialog dialog(this, "Enter pattern:", "Select");
        if (dialog.ShowModal() == wxID_OK)
        {
            for (long i = 0; i < GetItemCount(); i++)
            {
                ItemInfo *info = (ItemInfo*) GetItemData(i);
                if (info && info->name != "..")
                {
                    if (wxMatchWild(dialog.GetValue(), info->name))
                    {
                        SetItemTextColour(i, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
                        selection.erase(i);
                    }
                }
            }
        }
    }
}
void MainListView::OnActivated(wxListEvent& event)
{
    ItemInfo* info = (ItemInfo*) event.GetData();
    if (info)
    {
        if (info->isDirectory)
        {
            wxString path = info->fullPath;
            if (!ChangePath(path, info->name == ".."))
                wxMessageBox("Can't view " + info->fullPath, "Error", wxOK | wxICON_ERROR);
        }
        else
        {
            wxSetWorkingDirectory(currentPath);
            InvokeDefaultItem((HWND) GetParent()->GetHWND(), 0, info->fullPath, 0, 0);
        }
    }
}
bool MainListView::UpdateList(const wxString &path, bool forceRefresh)
{
    DirectoryCache *directoryCache = ((App *)wxTheApp)->GetDirectoryCache();
    vector<ItemInfo*> content;
    wxBeginBusyCursor();
    if (directoryCache->GetDirectoryContent(path, content, forceRefresh))
    {
        selection.clear();
        Freeze();
        if (itemDataNeedDeletion)
        {
            for (int  i = 0; i < this->GetItemCount(); i++)
                delete ((ItemInfo *) GetItemData(i));
            itemDataNeedDeletion = false;
        }
        DeleteAllItems();
        GetImageList(wxIMAGE_LIST_SMALL)->RemoveAll();
        long id = 0;
        for (unsigned int i = 0; i < content.size(); i++)
        {
            ItemInfo *info = content[i];
            wxListItem item;
            item.SetText(info->name);
            item.SetData(info);
            item.SetColumn(0);
            item.SetId(id);
            map<wxString, FileProperties>::iterator filePropertiesIterator = filePropertiesChanges.find(info->fullPath);
            if (filePropertiesIterator != filePropertiesChanges.end())
            {
                if (filePropertiesIterator->second.changed & FILE_PROPERTY_SIZE)
                    info->size = filePropertiesIterator->second.size.QuadPart;
                if (filePropertiesIterator->second.changed & FILE_PROPERTY_ICON)
                    info->icon = filePropertiesIterator->second.icon;
            }
            if (info->icon.IsOk())
            {
                int iconIndex = GetImageList(wxIMAGE_LIST_SMALL)->Add(info->icon);
                item.SetImage(iconIndex);
            }
            else
            {
                item.SetImage(-1);
                ((App *) wxTheApp)->GetBackgroundIconLoader()->RequestIcon(info->fullPath, info->isDirectory);
            }

            InsertItem(item);
            if (!info->isDirectory)
            {
                wxString size;
                if (info->size < 1024)
                    size.Printf("%d Bytes", (int) info->size);
                else if (info->size < 1024 * 1024)
                    size.Printf("%.1f KB", (double) info->size / 1024.0);
                else
                    size.Printf("%.1f MB", (double) info->size / (1024.0 * 1024.0));

                SetItem(id, 1, size);
            }


            id++;
        }
        map<wxString, long>::iterator i = selectionMap.find(path);
        if (i != selectionMap.end())
        {
            if (i->second < GetItemCount())
            {
                this->Select(i->second, true);
                this->Focus(i->second);
            }
            else
            {
                this->Select(GetItemCount() - 1, true);
                this->Focus(GetItemCount() - 1);
            }
        }
        else
        {
            this->Select(0, true);
            this->Focus(0);
        }
        filePropertiesChanges.clear();
        Thaw();
        wxEndBusyCursor();
        return true;
    }
    wxEndBusyCursor();
    return false;
}
void MainListView::DirectoryChanged(const wxString &path)
{
    wxString topPath, topPathName;
    wxFileName::SplitPath(currentPath, &topPath, &topPathName, NULL, wxPATH_NATIVE);

    if (currentPath == path)
    {
        if (!ChangePath(currentPath))
        {
            if (itemDataNeedDeletion)
            {
                for (int  i = 0; i < this->GetItemCount(); i++)
                    delete ((ItemInfo *) GetItemData(i));
            }
            itemDataNeedDeletion = true;
            Freeze();
            DeleteAllItems();
            GetImageList(wxIMAGE_LIST_SMALL)->RemoveAll();
            if (!topPathName.IsEmpty())
            {
                ItemInfo *info = new ItemInfo;

                info->isDirectory = true;
                info->fullPath = topPath;
                info->name = "..";
                info->icon = GetIconFromFile(info->fullPath, info->isDirectory);

                wxListItem item;
                item.SetText(info->name);
                item.SetData(info);
                item.SetColumn(0);
                item.SetId(0);
                int iconIndex = GetImageList(wxIMAGE_LIST_SMALL)->Add(info->icon);
                item.SetImage(iconIndex);
                InsertItem(item);
                Select(0);
                Focus(0);
            }
            Thaw();
        }
    }
    else if (topPath == path)
    {
        if (!wxDirExists(currentPath))
        {
            if (itemDataNeedDeletion)
            {
                for (int  i = 0; i < this->GetItemCount(); i++)
                    delete ((ItemInfo *) GetItemData(i));
            }
            itemDataNeedDeletion = true;
            Freeze();
            DeleteAllItems();
            GetImageList(wxIMAGE_LIST_SMALL)->RemoveAll();
            if (!topPathName.IsEmpty())
            {
                ItemInfo *info = new ItemInfo;

                info->isDirectory = true;
                info->fullPath = topPath;
                info->name = "..";
                info->icon = GetIconFromFile(info->fullPath, info->isDirectory);

                wxListItem item;
                item.SetText(info->name);
                item.SetData(info);
                item.SetColumn(0);
                item.SetId(0);
                int iconIndex = GetImageList(wxIMAGE_LIST_SMALL)->Add(info->icon);
                item.SetImage(iconIndex);
                InsertItem(item);
                Select(0);
                Focus(0);
            }
            Thaw();
        }
    }
}
void MainListView::OnKeyDown(wxKeyEvent &event)
{
    if ((event.GetKeyCode() == WXK_SPACE) && (!currentPath.empty()))
    {
        ItemInfo *info = (ItemInfo*) GetItemData(GetFirstSelected());
        if (info)
        {
            if (info->name != "..")
            {
                if (selection.find(GetFirstSelected()) != selection.end())
                {
                    selection.erase(GetFirstSelected());
                    SetItemTextColour(GetFirstSelected(), wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
                }
                else
                {
                    selection.insert(GetFirstSelected());
                    SetItemTextColour(GetFirstSelected(), colorDatabase.Find("RED"));
                }
            }
        }
        return;
    }
    else if ((event.AltDown() && event.GetKeyCode() == WXK_F1 && id == 0) || (event.AltDown() && event.GetKeyCode() == WXK_F2 && id == 1))
    {
        ShowDriveSelection();
    }
    else if ((event.AltDown() && event.GetKeyCode() == WXK_F1 && id == 1) || (event.AltDown() && event.GetKeyCode() == WXK_F2 && id == 0))
    {
        GetOtherMainListView()->ShowDriveSelection();
        GetOtherMainListView()->SetFocus();
    }
    else if (event.GetKeyCode() == WXK_TAB)
    {
        GetOtherMainListView()->SetFocus();
        return;
    }
    else if ((event.GetKeyCode() == 'R' && event.ControlDown()) && (!currentPath.empty()))
    {
        ChangePath(currentPath, true, true);
        return;
    }
    else if (event.GetKeyCode() == '~' && event.ControlDown())
    {
        ChangePath(wxFileName::GetHomeDir());
        return;
    }
    event.Skip();
}
bool MainListView::ChangePath(const wxString &path, bool useSelectioMap, bool forceRefresh)
{
    if (!useSelectioMap)
        selectionMap.erase(path);
    if (currentPath != "")
        selectionMap[currentPath] = GetFirstSelected();
    if (UpdateList(path, forceRefresh))
    {
        if (currentPath != "")
            ((App*)wxTheApp)->GetDirectoryCache()->MarkUnused(currentPath);
        currentPath = path;
        ((App*) wxTheApp)->GetMainFrame()->GetCompisiteFilePanel(id)->GetPathText()->ChangeValue(path);
        return true;
    }
    return false;
}
void MainListView::ShowDriveSelection()
{
    if (currentPath != "")
        ((App*)wxTheApp)->GetDirectoryCache()->MarkUnused(currentPath);
    currentPath = "";
    if (itemDataNeedDeletion)
    {
        for (int  i = 0; i < this->GetItemCount(); i++)
            delete ((ItemInfo *) GetItemData(i));
    }
    itemDataNeedDeletion = true;
    wxArrayString volumes = wxFSVolume::GetVolumes();
    selection.clear();
    Freeze();
    DeleteAllItems();
    GetImageList(wxIMAGE_LIST_SMALL)->RemoveAll();
    long id = 0;
    for (unsigned int i = 0; i < volumes.size(); i++)
    {
        ItemInfo *info = new ItemInfo;
        info->fullPath = info->name = volumes[i];
        info->isDirectory = true;
        info->icon = GetIconFromFile(volumes[i], true);
        wxListItem item;
        item.SetText(info->name);
        item.SetData(info);
        item.SetColumn(0);
        item.SetId(id);
        int iconIndex = GetImageList(wxIMAGE_LIST_SMALL)->Add(info->icon);
        item.SetImage(iconIndex);
        InsertItem(item);
        id++;
    }
    Select(0);
    Focus(0);
    Thaw();
}
MainListView *MainListView::GetOtherMainListView()
{
    MainFrameImpl *frame = ((App*) wxTheApp)->GetMainFrame();
    return frame->GetCompisiteFilePanel(1 - id)->GetMainListView();
}
wxString MainListView::GetCurrentPath()
{
    return currentPath;
}
void MainListView::OnBeginDrag(wxListEvent& event)
{
    wxFileDataObject data;
    if (selection.size() == 0)
    {
        ItemInfo *info = (ItemInfo*) GetItemData(event.GetIndex());
        if (info && info->name != "..")
            data.AddFile(info->fullPath);
    }
    else
    {
        for (set<long>::iterator i = selection.begin(); i != selection.end(); i++)
        {
            ItemInfo *info = (ItemInfo*) GetItemData(*i);
            if (info && info->name != "..")
                data.AddFile(info->fullPath);
        }
    }

    wxDropSource dragSource(this);
    dragSource.SetData(data);

    dragSource.DoDragDrop(wxDrag_AllowMove);
}
MainListView::FileDropTarget::FileDropTarget(MainListView *view) : owner(view)
{
    SetDataObject(new wxFileDataObject);
}

wxDragResult MainListView::FileDropTarget::OnData(wxCoord x, wxCoord y, wxDragResult def)
{
    if ( !GetData() )
        return wxDragNone;

    wxFileDataObject *dobj = (wxFileDataObject *)m_dataObject;
    FileOperation fop;
    if (def == wxDragCopy)
        fop.SetType(FOP_COPY);
    else if (def == wxDragMove)
        fop.SetType(FOP_MOVE);

    const wxArrayString &files = dobj->GetFilenames();
    for (unsigned int i = 0; i < files.size(); i++)
    {
        wxFileName file(files[i]);
        if (file.IsDir())
            fop.AddDirectory(files[i]);
        else
            fop.AddFile(files[i]);
    }
    FileOperationManager *fopm = ((App*) wxTheApp)->GetFileOperationManager();
    fop.SetDestinationPath(owner->GetCurrentPath());
    wxPanel *panel;
    wxGauge *gauge;
    wxButton *cancelButton;
    wxStaticText *descriptionText;
    MainFrameImpl *frame = ((App*) wxTheApp)->GetMainFrame();
    frame->AddJobWidgets(panel, gauge, cancelButton, descriptionText);
    WorkWatcherPtr workWatcher(new WorkWatcherImpl(panel, gauge, cancelButton, descriptionText));
    fop.SetWorkWatcher(workWatcher);
    fop.RegisterFilePropertiesWatcher(owner);
    fop.RegisterFilePropertiesWatcher(owner->GetOtherMainListView());
    cancelButton->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &WorkWatcherImpl::OnCancelButtonPressed, (WorkWatcherImpl*)workWatcher.get());
    fopm->PerformOperation(fop);

    return def;
}

void MainListView::FilePropertyChanged(const wxString &fullPath, FileProperties &fileProperties)
{
    for (int i = 0; i < GetItemCount(); i++)
    {
        ItemInfo *info = (ItemInfo *) GetItemData(i);
        if (info->fullPath == fullPath)
        {
            if (fileProperties.changed & FILE_PROPERTY_SIZE)
            {
                info->size = fileProperties.size.QuadPart;
                wxString size;
                if (info->size < 1024)
                    size.Printf("%d Bytes", (int) info->size);
                else if (info->size < 1024 * 1024)
                    size.Printf("%.1f KB", (double) info->size / 1024.0);
                else
                    size.Printf("%.1f MB", (double) info->size / (1024.0 * 1024.0));

                SetItem(i, 1, size);
            }
            if (fileProperties.changed & FILE_PROPERTY_ICON)
            {
                info->icon = fileProperties.icon;
                int iconIndex = GetImageList(wxIMAGE_LIST_SMALL)->Add(info->icon);
                SetItemImage(i, iconIndex);
            }
            return;
        }
    }
    map<wxString, FileProperties>::iterator filePropertiesIterator = filePropertiesChanges.find(fullPath);
    if (filePropertiesIterator != filePropertiesChanges.end())
    {
        if (fileProperties.changed & FILE_PROPERTY_SIZE)
        {
            filePropertiesIterator->second.changed |= FILE_PROPERTY_SIZE;
            filePropertiesIterator->second.size = fileProperties.size;
        }
        if (fileProperties.changed & FILE_PROPERTY_ICON)
        {
            filePropertiesIterator->second.changed |= FILE_PROPERTY_ICON;
            filePropertiesIterator->second.icon = fileProperties.icon;
        }
    }
    else
        filePropertiesChanges[fullPath] = fileProperties;
}